package com.itheima.prize.api.action;

import com.alibaba.fastjson.JSON;
import com.itheima.prize.api.config.LuaScript;
import com.itheima.prize.commons.config.RabbitKeys;
import com.itheima.prize.commons.config.RedisKeys;
import com.itheima.prize.commons.db.entity.*;
import com.itheima.prize.commons.db.mapper.CardGameMapper;
import com.itheima.prize.commons.db.service.CardGameService;
import com.itheima.prize.commons.utils.ApiResult;
import com.itheima.prize.commons.utils.RedisUtil;
import com.sun.org.apache.bcel.internal.generic.IF_ACMPEQ;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;

@RestController
@RequestMapping("/api/act")
@Api(tags = {"抽奖模块"})
public class ActController {

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private LuaScript luaScript;

    @GetMapping("/limits/{gameid}")
    @ApiOperation(value = "剩余次数")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "gameid", value = "活动id", example = "1", required = true)
    })
    public ApiResult<Object> limits(@PathVariable int gameid, HttpServletRequest request) {
        //获取活动基本信息
        CardGame game = (CardGame) redisUtil.get(RedisKeys.INFO + gameid);
        if (game == null) {
            return new ApiResult<>(-1, "活动未加载", null);
        }
        //获取当前用户
        HttpSession session = request.getSession();
        CardUser user = (CardUser) session.getAttribute("user");
        if (user == null) {
            return new ApiResult(-1, "未登陆", null);
        }
        //用户可抽奖次数
        Integer enter = (Integer) redisUtil.get(RedisKeys.USERENTER + gameid + "_" + user.getId());
        if (enter == null) {
            enter = 0;
        }
        //根据会员等级，获取本活动允许的最大抽奖次数
        Integer maxenter = (Integer) redisUtil.hget(RedisKeys.MAXENTER + gameid, user.getLevel() + "");
        //如果没设置，默认为0，即：不限制次数
        maxenter = maxenter == null ? 0 : maxenter;

        //用户已中奖次数
        Integer count = (Integer) redisUtil.get(RedisKeys.USERHIT + gameid + "_" + user.getId());
        if (count == null) {
            count = 0;
        }
        //根据会员等级，获取本活动允许的最大中奖数
        Integer maxcount = (Integer) redisUtil.hget(RedisKeys.MAXGOAL + gameid, user.getLevel() + "");
        //如果没设置，默认为0，即：不限制次数
        maxcount = maxcount == null ? 0 : maxcount;

        //幸运转盘类，先给用户随机剔除，再获取令牌，有就中，没有就说明抢光了
        //一般这种情况会设置足够的商品，卡在随机上
        Integer randomRate = (Integer) redisUtil.hget(RedisKeys.RANDOMRATE + gameid, user.getLevel() + "");
        if (randomRate == null) {
            randomRate = 100;
        }

        Map map = new HashMap();
        map.put("maxenter", maxenter);
        map.put("enter", enter);
        map.put("maxcount", maxcount);
        map.put("count", count);
        map.put("randomRate", randomRate);

        return new ApiResult<>(1, "成功", map);
    }

    @GetMapping("/go/{gameid}")
    @ApiOperation(value = "抽奖")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "gameid", value = "活动id", example = "1", required = true)
    })
    public ApiResult<Object> act(@PathVariable int gameid, HttpServletRequest request) {
        //TODO：任务6.1-抽奖业务-抽奖接口

        CardGame game = (CardGame) redisUtil.get(RedisKeys.INFO + gameid);
        //查看活动是否开始或已结束
        if (game == null || game.getStarttime().after(new Date())) {
            return (new ApiResult<>(-1, "活动未开始", null));
        }
        if (game.getEndtime().before(new Date())) {
            return (new ApiResult<>(-1, "活动已结束", null));
        }

        long gameStartTime = game.getStarttime().getTime();
        long gameEndTime = game.getEndtime().getTime();
        long duration = gameEndTime - gameStartTime; //活动持续时间 按ms计算
        long expireTime = duration / 1000 + 3600; //缓存过期时间  s

        //检查是否登录
        CardUser user = (CardUser) request.getSession().getAttribute("user");
        if (user == null) {
            return (new ApiResult<>(-1, "未登录", null));
        }
        Integer userId = user.getId();   //userid
        //根据活动id（主键）用户等级（filed）到redis里查它的最大中奖次数（value）
        Integer maxGoal = (Integer) redisUtil.hget(RedisKeys.MAXGOAL + gameid, user.getLevel() + "");
        //拿到最大抽奖次数
        Integer maxEnter = (Integer) redisUtil.hget(RedisKeys.MAXENTER + gameid, user.getLevel() + "");
        if (maxEnter ==null) {
            maxEnter = 0;
        }
        //拿到用户已抽奖次数
        Integer enter = (Integer) redisUtil.get(RedisKeys.USERENTER + gameid + "_" + user.getId());
        if (enter == null) {
            enter=0;
            redisUtil.set(RedisKeys.USERENTER + gameid + "_" + userId, 0, expireTime);
            //向队列发送消息，记录用户所参加的此次活动
            CardUserGame cardUserGame = new CardUserGame();
            cardUserGame.setUserid(userId);
            cardUserGame.setGameid(gameid);
            cardUserGame.setCreatetime(new Date());
            //RabbitMq传输对象的时候，使用FastJson将对象转为字符串后传输，可以避免很多问题，接收到
            //消息后，再转回对应对象。
            String cardUserGameString = JSON.toJSONString(cardUserGame);
            rabbitTemplate.convertAndSend("prize_exchange_direct", "prize_queue_play", cardUserGameString);
        }
        //判断抽奖总次数是否用完
        if (maxEnter != 0 && enter >= maxEnter) {
            return (new ApiResult<>(-1, "您的抽奖次数已用完", null));
        }

        //判断是否已达到最大中奖数
        Long token = luaScript.tokenCheck(gameid, userId, maxGoal);//拿到luaScript的返回值（或token或错误码）
        if (token == -1) {
            return (new ApiResult<>(-1, "您已达到最大中奖数", null));
        }
        //判断奖品是不是已经抽光
        if (token == -2) {
            return (new ApiResult<>(-1, "奖品已抽光", null));
        }
        //若没中奖
        if (token == 0) {
            redisUtil.incr(RedisKeys.USERENTER + gameid + "_" + user.getId(), 1);
            return (new ApiResult<>(-1, "很遗憾，未中奖", null));
        } else {  //中奖！！！
            //根据key-value拿到奖品实例
            CardProductDto product = (CardProductDto) redisUtil.get(RedisKeys.TOKEN + gameid + "_" + token);
            //用户抽奖总次数加 1
            redisUtil.incr(RedisKeys.USERENTER + gameid + "_" + user.getId(), 1);
            //异步传递用户中奖信息
            CardUserHit cardUserHit = new CardUserHit();
            cardUserHit.setUserid(userId);
            cardUserHit.setGameid(gameid);
            cardUserHit.setProductid(product.getId());
            cardUserHit.setHittime(new Date());
            String cardUserHitString = JSON.toJSONString(cardUserHit);

            rabbitTemplate.convertAndSend("prize_exchange_direct", "prize_queue_hit", cardUserHitString);
            return (new ApiResult<>(1, "恭喜中奖！！！", product));
        }


    }
  /*  Date now = new Date();
    //获取活动基本信息
    CardGame game = (CardGame) redisUtil.get(RedisKeys.INFO+gameid);
    //如果活动信息还没加载进redis，无效
        if (game == null){
        return new ApiResult(-1,"活动未加载",null);
    }
    //判断活动是否开始
    //如果活动已经加载，预热完成，但是开始时间 > 当前时间，也无效
        if (game.getStarttime().after(now)){
        return new ApiResult(-1,"活动未开始",null);
    }
    //判断活动是否已结束
        if (now.after(game.getEndtime())){
        return new ApiResult(-1,"活动已结束",null);
    }
    //获取当前用户
    HttpSession session = request.getSession();
    CardUser user = (CardUser) session.getAttribute("user");
        if (user == null){
        return new ApiResult(-1,"未登陆",null);
    }else{
        //第一次抽奖，发送消息队列，用于记录参与的活动（redis分布式锁）
        if (redisUtil.setNx(RedisKeys.USERGAME+user.getId()+"_"+gameid,1,(game.getEndtime().getTime() - now.getTime())/1000)){
            //持久化抽奖记录，扔给消息队列处理
            CardUserGame userGame = new CardUserGame();
            userGame.setUserid(user.getId());
            userGame.setGameid(gameid);
            userGame.setCreatetime(new Date());
            rabbitTemplate.convertAndSend(RabbitKeys.EXCHANGE_DIRECT,RabbitKeys.QUEUE_PLAY, JSON.toJSONString(userGame));
        }
    }

    //根据会员等级，获取本活动允许的最大抽奖次数
    Integer maxenter = (Integer) redisUtil.hget(RedisKeys.MAXENTER+gameid,user.getLevel()+"");
    //如果没设置，默认为0，即：不限制次数
    maxenter = maxenter==null ? 0 : maxenter;

    //次数对比
        if (maxenter > 0){
        //用户可抽奖次数
        long enter = redisUtil.incr(RedisKeys.USERENTER+gameid+"_"+user.getId(),1);
        if (enter > maxenter){
            //如果达到最大次数，不允许抽奖
            return new ApiResult(-1,"您的抽奖次数已用完",null);
        }
    }

    //根据会员等级，获取本活动允许的最大中奖数
    Integer maxgoal = (Integer) redisUtil.hget(RedisKeys.MAXGOAL+gameid,user.getLevel()+"");
    //如果没设置，默认为0，即：不限制次数
    maxgoal = maxgoal==null ? 0 : maxgoal;


    //以上校验全部过关，进入下一步：拿token
    //也就是决定中不中最关键的一步，拿到合法token就中奖
    Long token;
        switch (game.getType()) {
        //时间随机
        case 1:
            //随即类比较麻烦，按设计时序图走
            //lua调redis
            token = luaScript.tokenCheck(gameid,user.getId(),maxgoal);
            if(token == 0){
                return new ApiResult(0,"未中奖",null);
            }else if(token == -1){
                return new ApiResult(-1,"您已达到最大中奖数",null);
            }else if(token == -2){
                return new ApiResult(-1,"奖品已抽光",null);
            }

            break;

        case 2:

            //瞬间秒杀类简单，直接获取令牌，有就中，没有就说明抢光了
            token = (Long) redisUtil.leftPop(RedisKeys.TOKENS+gameid);
            if (token == null){
                //令牌已用光，说明奖品抽光了
                return new ApiResult(-1,"奖品已抽光",null);
            }

            break;

        case 3:

            //幸运转盘类，先给用户随机剔除，再获取令牌，有就中，没有就说明抢光了
            //一般这种情况会设置足够的商品，卡在随机上
            Integer randomRate = (Integer) redisUtil.hget(RedisKeys.RANDOMRATE+gameid,user.getLevel()+"");
            if (randomRate == null){
                randomRate = 100;
            }
            //注意这里的概率设计思路：
            //每次请求取一个0-100之间的随机数，如果这个数没有落在范围内，直接返回未中奖
            if( new Random().nextInt(100) > randomRate ){
                return new ApiResult(0,"未中奖",null);
            }

            token = (Long) redisUtil.leftPop(RedisKeys.TOKENS+gameid);
            if (token == null){
                //令牌已用光，说明奖品抽光了
                return new ApiResult(-1,"奖品已抽光",null);
            }

            break;

        default:
            return new ApiResult(-1,"不支持的活动类型",null);

    }//end switch


    //以上逻辑走完，拿到了合法的token，说明很幸运，中奖了！
    //抽中的奖品：
    CardProduct product = (CardProduct) redisUtil.get(RedisKeys.TOKEN + gameid +"_"+token);
    //投放消息给队列，中奖后的耗时业务，交给消息模块处理
    CardUserHit hit = new CardUserHit();
        hit.setGameid(gameid);
        hit.setHittime(now);
        hit.setProductid(product.getId());
        hit.setUserid(user.getId());
        rabbitTemplate.convertAndSend(RabbitKeys.EXCHANGE_DIRECT,RabbitKeys.QUEUE_HIT, JSON.toJSONString(hit));

    //返回给前台中奖信息
        return new ApiResult(1,"恭喜中奖",product);*/

    @GetMapping("/info/{gameid}")
    @ApiOperation(value = "缓存信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "gameid", value = "活动id", example = "1", required = true)
    })
    public ApiResult info(@PathVariable int gameid) {
        //TODO：任务5.2-缓存预热-查询验证缓存
        //明确一下返回的map里要放的东西：
        //1.活动基本信息的key和cardgame实例
        //2.切记 用户等级和最大中奖次数（最大抽奖次数）都是我们要查的值 所以key为活动id加rediskey
        //3.令牌桶里的令牌（作为key）和它绑定的奖品productdto实例

        //新建一个map集合用来给ApiResult 的data赋值  提示  linkedhashmap使用双向链表维护插入顺序
        Map map = new LinkedHashMap<>();
        //把活动基本信息放进map里
        map.put(RedisKeys.INFO + gameid, redisUtil.get(RedisKeys.INFO + gameid));
        //从redis中取出 tokenList
        List<Object> tokenList = redisUtil.lrange(RedisKeys.TOKENS + gameid, 0, -1);
        //新建一个map集合存储token和它绑定的奖品
        Map tokenMap = new LinkedHashMap<>();
        //用lamda遍历出tokenList里的token 并把令牌桶里的令牌进行时间转换后作为key 它绑定的奖品productdto实例作为map的值
        //key:new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(Long.valueOf(o.toString())/1000))
        tokenList.forEach(o -> tokenMap.put(
                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(Long.valueOf(o.toString()) / 1000))
                , redisUtil.get(RedisKeys.TOKEN + gameid + "_" + o))
        );
        map.put(RedisKeys.TOKENS + gameid, tokenMap);

        //从redis中取出最大中奖次数（最大抽奖次数）不同等级用户所对应的次数放进map
        map.put(RedisKeys.MAXGOAL + gameid, redisUtil.hmget(RedisKeys.MAXGOAL + gameid));
        map.put(RedisKeys.MAXENTER + gameid, redisUtil.hmget(RedisKeys.MAXENTER + gameid));
        //返回new ApiResult(200,"缓存信息",map)
        return new ApiResult(200, "缓存信息", map);

    }
}
